package br.com.scaled.audio;

import org.hermit.dsp.FFTTransformer;

import br.com.scaled.view.Note;

public class AudioAnalyser {

	 	// Debugging tag.
		private static final String TAG = "audioAnalyser";

	    // The desired sampling rate for this analyser, in samples/sec.
	    private int sampleRate = 8000;

	    // Audio input block size, in samples.
	    private int inputBlockSize = 256;
	    
	    // The desired decimation rate for this analyser.  Only 1 in
	    // sampleDecimate blocks will actually be processed.
	    private int sampleDecimate = 1;
	   
	    // Our audio input device.
	    private final AudioReader audioReader;

	    // Fourier Transform calculator we use for calculating the spectrum
	    // and sonagram.
	    private FFTTransformer spectrumAnalyser;
	    
	    // Buffered audio data, and sequence number of the latest block.
	    private short[] audioData;
	    private long audioSequence = 0;
	    
	    // Sequence number of the last block we processed.
	    private long audioProcessed = 0;

	    // Analysed audio spectrum data; history data for each frequency
	    // in the spectrum; index into the history data; and buffer for
	    // peak frequencies.
	    private float[] spectrumData;
	    
	    private Note nota = null;

	    /**
		 * Create a WindMeter instance.
		 * 
	     * @param   parent          Parent surface.
		 */
	    public AudioAnalyser() {
	        super();
	        
	        audioReader = new AudioReader();
	        
	        spectrumAnalyser = new FFTTransformer(inputBlockSize);
	        
	        // Allocate the spectrum data.
	        spectrumData = new float[inputBlockSize / 2];
	    }
	    
	    // ******************************************************************** //
	    // Configuration.
	    // ******************************************************************** //

	    /**
	     * Set the sample rate for this instrument.
	     * 
	     * @param   rate        The desired rate, in samples/sec.
	     */
	    public void setSampleRate(int rate) {
	        sampleRate = rate;
	    }
	    
	    /**
	     * Set the input block size for this instrument.
	     * 
	     * @param   size        The desired block size, in samples.  Typical
	     *                      values would be 256, 512, or 1024.  Larger block
	     *                      sizes will mean more work to analyse the spectrum.
	     */
	    public void setBlockSize(int size) {
	        inputBlockSize = size;

	        spectrumAnalyser = new FFTTransformer(inputBlockSize);

	        // Allocate the spectrum data.
	        spectrumData = new float[inputBlockSize / 2];
	    }
	    
	    /**
	     * Set the decimation rate for this instrument.
	     * 
	     * @param   rate        The desired decimation.  Only 1 in rate blocks
	     *                      will actually be processed.
	     */
	    public void setDecimation(int rate) {
	        sampleDecimate = rate;
	    }
	    
	    // ******************************************************************** //
	    // Run Control.
	    // ******************************************************************** //

	    /**
	     * The application is starting.  Perform any initial set-up prior to
	     * starting the application.  We may not have a screen size yet,
	     * so this is not a good place to allocate resources which depend on
	     * that.
	     */
	    public void appStart() {
	    }


	    /**
	     * We are starting the main run; start measurements.
	     */
	    public void measureStart() {
	        audioProcessed = audioSequence = 0;
	        
	        audioReader.startReader(sampleRate, inputBlockSize * sampleDecimate, new AudioReader.Listener() {
	            @Override
	            public final void onReadComplete(short[] buffer) {
	                receiveAudio(buffer);
	            }
	            @Override
	            public void onReadError(int error) {
	            }
	        });
	    }


	    /**
	     * We are stopping / pausing the run; stop measurements.
	     */
	    public void measureStop() {
	        audioReader.stopReader();
	    }
	    

	    /**
	     * The application is closing down.  Clean up any resources.
	     */
	    public void appStop() {
	    }
	    
	    public Note getNote(){
	    	nota = new Note();
	    	return nota;
	    }
	    
	    // ******************************************************************** //
	    // Audio Processing.
	    // ******************************************************************** //

	    /**
	     * Handle audio input.  This is called on the thread of the audio
	     * reader.
	     * 
	     * @param   buffer      Audio data that was just read.
	     */
	    private final void receiveAudio(short[] buffer) {
	        // Lock to protect updates to these local variables.  See run().
	        synchronized (this) {
	            audioData = buffer;
	            ++audioSequence;
	        }
	    }
	    
	    
	    // ******************************************************************** //
	    // Main Loop.
	    // ******************************************************************** //

	    /**
	     * Update the state of the instrument for the current frame.
	     * This method must be invoked from the doUpdate() method of the
	     * application's {@link SurfaceRunner}.
	     * 
	     * <p>Since this is called frequently, we first check whether new
	     * audio data has actually arrived.
	     * 
	     * @param   now         Nominal time of the current frame in ms.
	     */
	    public final void doUpdate(long now) {
	        short[] buffer = null;
	        synchronized (this) {
	            if (audioData != null && audioSequence > audioProcessed) {
//	                parentSurface.statsCount(1, (int) (audioSequence - audioProcessed - 1));
	                audioProcessed = audioSequence;
	                buffer = audioData;
	            }
	        }

	        // If we got data, process it without the lock.
	        if (buffer != null)
	            processAudio(buffer);
	      
//	        TODO
//	        if (readError != AudioReader.Listener.ERR_OK)
//	            processError(readError);
	    }


	    /**
	     * Handle audio input.  This is called on the thread of the
	     * parent surface.
	     * 
	     * @param   buffer      Audio data that was just read.
	     */
	    private final void processAudio(short[] buffer) {
	        // Process the buffer.  While reading it, it needs to be locked.
	        synchronized (buffer) {
	            // Calculate the power now, while we have the input
	            // buffer; this is pretty cheap.
	            final int len = buffer.length;

	            // If we have a spectrum or sonagram analyser, set up the FFT input data.
                spectrumAnalyser.setInput(buffer, len - inputBlockSize, inputBlockSize);

	            // Tell the reader we're done with the buffer.
	            buffer.notify();
	        }

            // Do the (expensive) transformation.
            // The transformer has its own state, no need to lock here.
            spectrumAnalyser.transform();

            // Get the FFT output.
            spectrumAnalyser.getResults(spectrumData);

            nota.update(spectrumData);
	    }
	    
//	    TODO verificar possibilidade de exibir a nota na propria classe
//	    private String frequenceNotes(float []buffer){
//			final double CONSTANTE = 1.05946309;
//			
//	    	int freq = 0;
//	    	float amp = 0;
//	    	for(int i = 0; i<buffer.length; i++){
//	    		if(buffer[i] > amp){
//	    			amp = buffer[i];
//	    			freq = i;
//	    		}
//	    	}
//
//	    	freq = (int) (freq * 31.25);
//	        
//	    	String notes[] = {"A","A#","B","C","C#","D", "D#", "E","F","F#","G","G#"};
//	    	
//	    	int i = -30;
//	    	double result = 0;
//	    	do {
//	    		i++;
//	    		result  = Math.pow(CONSTANTE, i) * 440;
//	    	} while (result <= freq);
//	    	
//	    	i = menosDistante(freq, i);
//	    	
//	    	int j = i%12;
//	    	if (j < 0) {
//	    		 j = 12+j;
//	    	}
//			String nota = notes[j];
//	    	Log.i(TAG, "freq: "+ freq+" i: "+ i  +" result: "+ result  + " j: "+ j + " Nota: " + nota);
//			return nota +" - "+ freq;
//		}
//		   
//	    private int menosDistante(int freq, int i) {
//			final double CONSTANTE = 1.05946309;
//			
//			double result1 = Math.pow(CONSTANTE, i-1) * 440;
//			
//	    	double result2 = Math.pow(CONSTANTE, i) * 440;
//
//	    	double result3 = Math.pow(CONSTANTE, i+1) * 440;
//	    	
//	    	int diferenca1 = (int) (freq - result1);
//	    	int diferenca2 = (int) (result2 - freq);
//	    	int diferenca3= (int) (result3 - freq);
//	    	
//	    	return (diferenca1 < diferenca2) ? i-1 : (diferenca2 < diferenca3 ? i : i +1);
//		}
	    
}
